﻿Imports DotNetNuke.Entities.Modules
Imports Aricie.DNN.Modules.LuceneSearch.ModuleProviders
Imports Aricie.DNN.Modules.LuceneSearch.Business
Imports Aricie.Services
Imports Aricie.DNN.Services
Imports Ventrian.NewsArticles
Imports Ventrian.NewsArticles.Components.CustomFields
Imports Lucene.Net.Documents


Namespace Aricie.DNN.Modules.LuceneSearch.ModuleProviders

    Public Class NewsArticlesProvider
        Implements ILuceneSearchableUpgrade
        Implements ILuceneFieldGlossary



#Region "Implement ILuceneSearchableUpgrade"

        ''' <summary>
        ''' Implementing ILuceneSearchableUpgrade allows you to add new fields to the search item, to be used for fine grained search queries, dedicated filter controls and extended result templates.
        ''' </summary>
        ''' <param name="portalId">The searchitem module parent portalId</param>
        ''' <param name="searchItem">The Native item to be upgraded. The initial SearchItemInfo item was already upgraded to an extended LuceneSearchItemInfo</param>
        ''' <remarks></remarks>
        Public Sub UpgradeSearchItem(ByVal portalId As Integer, ByRef searchItem As LuceneSearchItemInfo) Implements ILuceneSearchableUpgrade.UpgradeSearchItem

            'Retrieve article id from native searchItem to be upgraded with additional fields.
            Dim articleId As Integer = searchItem.GUID.Substring(searchItem.GUID.LastIndexOf("ArticleID=") + 10)

            'Instantiate Ventrian Newsarticle entities
            Dim objArticleController As New ArticleController
            Dim objArticle As ArticleInfo

            'Retrieve current article from Id
            objArticle = objArticleController.GetArticle(articleId)


            'Add image url, which is missing in the native ISearchable implementation
            'searchItem.ImagePath = objArticle.ImageUrl


            'Add article categories

            For Each currentCategory As CategoryInfo In objArticleController.GetArticleCategories(articleId)
                'The LuceneSearchItemInfo class already contains a Category property, which is used here.
                If String.IsNullOrEmpty(searchItem.Category) Then
                    searchItem.Category = currentCategory.Name + ";"c
                Else
                    searchItem.Category += currentCategory.Name + ";"c
                End If
                searchItem.Category = searchItem.Category.TrimEnd(";"c)

                'The ItemGroup property is used to ease grouping in the auto-complete skin object. Corresponding Resx keys are used to label those groups.
                If String.IsNullOrEmpty(searchItem.ItemGroup) Or Not searchItem.ItemGroup.StartsWith("NA-") Then
                    searchItem.ItemGroup = "NA-" + currentCategory.Name
                Else
                    searchItem.ItemGroup += "-" + currentCategory.Name
                End If
            Next



            'Add article tags

            Dim tags() As String = objArticle.Tags.Split(",")
            For Each strTag As String In tags
                'LuceneSearchItem class has Tab, Module, Content and compound Taxonomy properties modeled after DNN core Taxonomies
                searchItem.ContentTaxonomy.Add(strTag)
            Next



            ' Add article custom Fields

            'Custom fields don't have hardcoded properties associated to them so we'll need to attach them to custom lucene fields
            Dim factory As FieldFactory = FieldFactory.Instance

            ' Get Global Newsarticle custom field definitions
            Dim customFields As Dictionary(Of Integer, CustomFieldInfo) = GetCustomFields()

            ' For each Article CustomField values are retrieved as a hash table of ids/values 
            For Each articleCustomField As DictionaryEntry In objArticle.CustomList

                Dim customFieldId As Integer = DirectCast(articleCustomField.Key, Integer)
                Dim currentCustomField As CustomFieldInfo = Nothing
                'Find the CustomField Definition from Id
                If customFields.TryGetValue(customFieldId, currentCustomField) Then
                    'Add both an analyzed and non analyzed versions
                    searchItem.AdditionalFields.Add(factory.CreateField(glbCustomFieldPrefix & currentCustomField.Name, articleCustomField.Value))
                    searchItem.AdditionalFields.Add(factory.CreateField(glbCustomFieldUntokenizedPrefix & currentCustomField.Name, articleCustomField.Value))
                End If
            Next

        End Sub


#End Region



#Region "Implement ILuceneFieldGlossary"

        ''' <summary>
        ''' Implement ILuceneFieldGlossary to declare new custom fields definition to be inserted in the Lucene Index. 
        ''' </summary>
        ''' <returns>A list of Lucene Field definitions for each additional field</returns>
        ''' <remarks>Each field to be indexed should have a definition declared in one of the module and global glossaries.</remarks>
        Public Function GetFieldDefinitions() As IList(Of FieldDefinition) _
            Implements ILuceneFieldGlossary.GetFieldDefinitions

            'We want to return a list of addictional Field Definitions with dedicated names and Lucene indexing instructions
            Dim toReturn As List(Of FieldDefinition)

            'The only additional fields we are concerned with are Newsarticle custom fields (other fields are declared in the default standard glossary)

            'Retrieve custom fields from all Newsarticle module instances
            Dim customFields As Dictionary(Of Integer, CustomFieldInfo) = GetCustomFields()

            'Some custom fields might be declared concurrently in several Newsarticle instances, we use a dictionary to declare them only once.
            Dim tempDico As New Dictionary(Of String, FieldDefinition)
            For Each objCustomField As CustomFieldInfo In customFields.Values
                'For each Newsarticle custom Field, we declare 2 Lucene fields: 
                'The analyzed one uses a tokenizer and a localized stemmer according to the configuration, and is useful for free text search text boxes
                tempDico(glbCustomFieldPrefix & objCustomField.Name) = New FieldDefinition(glbCustomFieldPrefix & objCustomField.Name, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.NO, False, 1)
                'The other version keeps the field untouched, and is useful for value based filter controls like dropdownlists
                tempDico(glbCustomFieldUntokenizedPrefix & objCustomField.Name) = New FieldDefinition(glbCustomFieldUntokenizedPrefix & objCustomField.Name, Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.NO, False, 1)
            Next
            toReturn = New List(Of FieldDefinition)(tempDico.Values)
            Return toReturn
        End Function
#End Region

#Region "Private members"

        ''' <summary>
        ''' That method agregrates and returns a dictionary of the custom fields declared in all newsarticle instances of all portals of the current DNN instance
        ''' </summary>
        ''' <remarks>The result is kept in cache to avoid unnecessary db activity at indexing time</remarks>
        Private Function GetCustomFields() As Dictionary(Of Integer, CustomFieldInfo)
            Dim toReturn As Dictionary(Of Integer, CustomFieldInfo) = CacheHelper.GetGlobal(Of Dictionary(Of Integer, CustomFieldInfo))()
            If toReturn Is Nothing Then
                toReturn = New Dictionary(Of Integer, CustomFieldInfo)
                Dim mDefId As Integer = NukeHelper.GetModuleDefIdByModuleName(glbDesktopModuleName)
                If mDefId <> -1 Then
                    For Each myPortalID As Integer In NukeHelper.PortalIds()
                        Dim moduleDefFriendlyName As String = NukeHelper.ModuleDefinitionController.GetModuleDefinition(mDefId).FriendlyName
                        Dim instanceModules As ArrayList = NukeHelper.ModuleController.GetModulesByDefinition(myPortalID, moduleDefFriendlyName)
                        Dim customFields As List(Of CustomFieldInfo)
                        Dim objCustomFieldController As New CustomFieldController
                        For Each propModule As ModuleInfo In instanceModules
                            customFields = New List(Of CustomFieldInfo)(objCustomFieldController.List(propModule.ModuleID))
                            For Each customField As CustomFieldInfo In customFields
                                toReturn(customField.CustomFieldID) = customField
                            Next
                        Next
                    Next
                End If
                CacheHelper.SetGlobal(Of Dictionary(Of Integer, CustomFieldInfo))(toReturn)
            End If
            Return toReturn

        End Function

#End Region


        'Used to retrieve the list of module instances 
        Private Const glbDesktopModuleName As String = "NewsArticle"

        'Simple constants used for prefixing custom field names. The actual UI captions leverage the localization resx files with keys extracted from those names.
        Private Const glbCustomFieldPrefix As String = "NACustom"
        Private Const glbCustomFieldUntokenizedPrefix As String = "NAUCustom"


    End Class

End Namespace